﻿using System;
using System.Configuration;
using Core.View.Renders;
using Core.Controllers;
using Core.Model;
using Core.Model.Chunks;
using Core.Model.Types;
using SharpDX.D3DCompiler;
using SharpDX.DXGI;
using SharpDX.Direct3D10;
using SharpDX.Game.Model;
using SharpDX.Game.Model.Helpers;
using SharpDX.Windows;
using Device = SharpDX.Direct3D10.Device;

namespace SharpDX.Game.View.Renders
{
    public class SlimDxWordRenderer : WorldRenderer
    {
        #region Static methods

        public static SlimDxWordRenderer Create()
        {
            RenderForm form = new RenderForm();

            form.Width = Convert.ToInt32(ConfigurationManager.AppSettings["width"]);
            form.Height = Convert.ToInt32(ConfigurationManager.AppSettings["height"]);

            SlimDxWordRenderer result = new SlimDxWordRenderer(form);

            result.Initialize();

            return
                result;
        }

        #endregion

        #region Fields

        private SwapChain swapChain;
        private RenderTargetView renderView;
        private Effect effect;
        private EffectPass pass;
        private InputLayout layout;
        private EffectMatrixVariable wvpVariable = null;
        private EffectMatrixVariable worldVariable = null;
        private EffectMatrixVariable viewVariable = null;
        private EffectMatrixVariable projectionVariable = null;
        private EffectVectorVariable indexVariable = null;
        private EffectVectorVariable cameraPositionVariable = null;
        private EffectShaderResourceVariable textureVariable = null;

        private DepthStencilState DepthState;

        private DepthStencilView depthStencilView;
        #endregion

        #region ctor

        private SlimDxWordRenderer(RenderForm renderForm)
        {
            RenderForm = renderForm;
        }

        #endregion

        #region Properies

        public override IntPtr WindowHandle
        {
            get { return RenderForm.Handle; }
        }

        public override int WindowWidth
        {
            get { return RenderForm.Width; }
        }

        public override int WindowHeight
        {
            get { return RenderForm.Height; }
        }

        public RenderForm RenderForm;

        public Device _device;

        #endregion

        #region Public methods

        private Color4 HorizonColor = TypeCompatibleHelper.ConvertFromColor(World.HORIZONCOLOR);

        public override void BeginDraw()
        {
            _device.ClearRenderTargetView(renderView, HorizonColor);
            _device.ClearDepthStencilView(depthStencilView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1, 0);

            _device.OutputMerger.DepthStencilState = DepthState;            
        }

        public override void DrawTerrain(Camera camera, Chunk[] chunks)
        {
            wvpVariable.SetMatrix(TypeCompatibleHelper.ConvertFromMatrixF(new MatrixF(new MatrixF(camera.World,camera.View), camera.Projection)));
            worldVariable.SetMatrix(TypeCompatibleHelper.ConvertFromMatrixF(camera.World));
            viewVariable.SetMatrix(TypeCompatibleHelper.ConvertFromMatrixF(camera.View));
            projectionVariable.SetMatrix(TypeCompatibleHelper.ConvertFromMatrixF(camera.Projection));
            _device.InputAssembler.InputLayout = layout;

            cameraPositionVariable.Set(TypeCompatibleHelper.ConvertFromVector3F(camera.Position));

            BoundingFrustumF viewFrustum = new BoundingFrustumF(camera.View * camera.Projection);

            for (int i = 0; i < chunks.Length; i++)
            {
                Chunk chunk = chunks[i];

                if (chunk == null || chunk.IsReady == false)
                {
                    continue;
                }

                if (viewFrustum.FastIntersects(chunk.BoundingBox))
                {
                    indexVariable.Set(TypeCompatibleHelper.ConvertFromVector3F(chunk.Index3));

                    ((SlimDxChunk)chunk).DrawChunk(_device, pass);
                }
            }
        }

        public override void EndDraw()
        {
            swapChain.Present(0, PresentFlags.None);
        }

        #endregion

        #region Private methods

        private void Initialize()
        {
            var desc = new SwapChainDescription()
            {
                BufferCount = 1,
                ModeDescription =
                    new ModeDescription(RenderForm.Width, RenderForm.Height, new Rational(60, 1),
                                        Format.R8G8B8A8_UNorm),
                IsWindowed = true,
                OutputHandle = RenderForm.Handle,
                SampleDescription = new SampleDescription(1, 0),
                SwapEffect = SwapEffect.Discard,
                Usage = Usage.RenderTargetOutput,
            };

            Device.CreateWithSwapChain(null, DeviceCreationFlags.None, desc, out _device, out swapChain);

            //Stops Alt+enter from causing fullscreen skrewiness.
            Factory factory = swapChain.GetParent<Factory>();
            factory.MakeWindowAssociation(RenderForm.Handle, WindowAssociationFlags.IgnoreAll);

            renderView = new RenderTargetView(_device, Texture2D.FromSwapChain<Texture2D>(swapChain, 0));
            
            CreateDepthBuffer(RenderForm.Width, RenderForm.Height);

            _device.OutputMerger.SetTargets(depthStencilView, renderView);

            Viewport viewPort = new Viewport(0, 0, RenderForm.Width, RenderForm.Height, 0.0f, 1.0f);
            
            _device.Rasterizer.SetViewports(viewPort);

            var effectByteCode = ShaderBytecode.CompileFromFile("Content/Shaders/triangle.fx", "fx_4_0", ShaderFlags.EnableBackwardsCompatibility, EffectFlags.None);
            effect = new Effect(_device, effectByteCode);

            var technique = effect.GetTechniqueByIndex(0);
            pass = technique.GetPassByIndex(0);

            wvpVariable = effect.GetVariableByName("WVP").AsMatrix();
            worldVariable = effect.GetVariableByName("World").AsMatrix();
            viewVariable = effect.GetVariableByName("View").AsMatrix();
            projectionVariable = effect.GetVariableByName("Projection").AsMatrix();
            indexVariable = effect.GetVariableByName("Index").AsVector();
            cameraPositionVariable = effect.GetVariableByName("CameraPosition").AsVector();
            textureVariable = effect.GetVariableByName("Texture1").AsShaderResource();

            effect.GetVariableByName("HorizonColor").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.HORIZONCOLOR));
            effect.GetVariableByName("NightColor").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.NIGHTCOLOR));
            effect.GetVariableByName("MorningTint").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.MORNINGTINT));
            effect.GetVariableByName("EveningTint").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.EVENINGTINT));
            effect.GetVariableByName("SunColor").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.SUNCOLOR));
            effect.GetVariableByName("FogNear").AsScalar().Set(ChunkManager.FOGNEAR);
            effect.GetVariableByName("FogFar").AsScalar().Set(ChunkManager.FOGFAR);

            ShaderResourceView resource = new ShaderResourceView(_device, 
                Texture2D.FromFile<SharpDX.Direct3D10.Resource>(_device, @"Content\Textures\terrain1.png"));

            textureVariable.SetResource(resource);

            layout = new InputLayout(_device, pass.Description.Signature, BlockVertex.InputElements);
        }

        private void CreateDepthBuffer(int width, int height)
        {
            SampleDescription sampleDesc =
                new SampleDescription
                    {
                        Count = 1,
                        Quality = 0
                    };

            Texture2DDescription texDesc = new Texture2DDescription();
            texDesc.Width = width;
            texDesc.Height = height;
            texDesc.MipLevels = 1;
            texDesc.ArraySize = 1;
            texDesc.BindFlags = BindFlags.DepthStencil;
            texDesc.CpuAccessFlags = CpuAccessFlags.None;
            texDesc.Format = Format.D32_Float;
            texDesc.SampleDescription = sampleDesc;
            
            DepthStencilViewDescription stencilViewDesc = new DepthStencilViewDescription();

            stencilViewDesc.Dimension = DepthStencilViewDimension.Texture2D;
            stencilViewDesc.Format = texDesc.Format;
            //stencilViewDesc.MipSlice = 0;
            //stencilViewDesc.FirstArraySlice = 0;
            //stencilViewDesc.ArraySize = 1;

            using (Texture2D stencilTex = new Texture2D(_device, texDesc))
            {
                depthStencilView = new DepthStencilView(_device, stencilTex, stencilViewDesc);
            }
            
            DepthStencilStateDescription stencilStateDesc = new DepthStencilStateDescription();
            stencilStateDesc.IsDepthEnabled = true;
            stencilStateDesc.IsStencilEnabled = true;
            stencilStateDesc.DepthWriteMask = DepthWriteMask.All;
            stencilStateDesc.DepthComparison = Comparison.Less;
            stencilStateDesc.StencilReadMask = 0xFF;
            stencilStateDesc.StencilWriteMask = 0xFF;

            DepthStencilOperationDescription dsod = new DepthStencilOperationDescription();

            dsod.FailOperation = StencilOperation.Keep;
            dsod.DepthFailOperation = StencilOperation.Increment;
            dsod.PassOperation = StencilOperation.Keep;
            dsod.Comparison = Comparison.Always;
            stencilStateDesc.FrontFace = dsod;
            dsod.DepthFailOperation = StencilOperation.Decrement;
            stencilStateDesc.BackFace = dsod;

            DepthState = new DepthStencilState(_device, stencilStateDesc);

            RasterizerStateDescription rsd = new RasterizerStateDescription();
            rsd.FillMode = FillMode.Solid;
            rsd.CullMode = CullMode.Back;
            rsd.IsMultisampleEnabled = true;
            rsd.IsAntialiasedLineEnabled = false;
            rsd.IsDepthClipEnabled = false;
            rsd.IsScissorEnabled = false;

            _device.Rasterizer.State = new RasterizerState(_device, rsd);

            //BlendStateDescription blendDesc = new BlendStateDescription();
            //blendDesc.BlendOperation = BlendOperation.Add;
            //blendDesc.AlphaBlendOperation = BlendOperation.Add;
            //blendDesc.SourceAlphaBlend = BlendOption.Zero;
            //blendDesc.DestinationAlphaBlend = BlendOption.Zero;
            //blendDesc.SourceBlend = BlendOption.SourceColor;
            //blendDesc.DestinationBlend = BlendOption.Zero;
            //blendDesc.IsAlphaToCoverageEnabled = false;
            ////blendDesc.SetWriteMask(0, ColorWriteMaskFlags.All);
            ////blendDesc.SetBlendEnable(0, true);

            //_device.OutputMerger.BlendState = new BlendState(_device, blendDesc);   
        }

        #endregion

       
    }
}
